package com.hontech.bread.api

import com.hontech.bread.R
import com.hontech.bread.device.DeviceStatus
import com.hontech.bread.device.ResetReplenishEvent
import com.hontech.bread.device.mdb.Bill
import com.hontech.bread.device.mdb.Coin
import com.hontech.bread.device.ota
import com.hontech.bread.device.reset
import com.hontech.bread.ui.BaseActivity
import com.hontech.bread.ui.dialog.runProg
import com.hontech.bread.ui.dialog.showErr
import com.hontech.bread.ui.fragment.Vend2Fragment
import com.hontech.bread.utils.CPUTask
import com.hontech.bread.utils.ServiceException
import com.hontech.bread.utils.Version
import com.hontech.bread.utils.installApk2
import com.hontech.bread.utils.log
import com.hontech.bread.utils.postEvent
import com.hontech.bread.utils.stringById
import com.hontech.bread.utils.wifiRestart
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.Runnable
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.cbor.Cbor
import kotlinx.serialization.decodeFromByteArray
import kotlinx.serialization.encodeToByteArray
import java.io.IOException
import java.net.UnknownHostException
import kotlin.coroutines.CoroutineContext

private class HandlerDispatcher : CoroutineDispatcher() {

    override fun dispatch(context: CoroutineContext, block: Runnable) {
        CPUTask.post(block)
    }
}

private val scope: CoroutineScope = CoroutineScope(Job() + HandlerDispatcher())

private fun CoroutineScope.launchCatchAny(cb: suspend CoroutineScope.() -> Unit) {
    launch {
        try {
            cb()
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }
}


fun keepTask() {

    scope.launchCatchAny {

        loginKeep()
        if (!isLogin()) {
            return@launchCatchAny
        }

        doPoll()
    }
}

object ServerCmd {
    const val VEND_REFRESH = "/vend/refresh"

    const val STATUS_DEVICE = "/status/device"
    const val STATUS_COIN = "/status/coin"
    const val STATUS_BILL = "/status/bill"

    const val COIN_CHANGE = "/coin/change"
    const val SCREEN_SHOT = "/screen_shot"
    const val APP_VERSION = "/app_version"
    const val APP_UPDATE = "/app_update"
    const val MCU_UPDATE = "/mcu_update"
    const val GET_VEND = "/get/vend"
    const val UPLOAD_ORDER = "/upload_order"

    const val RESET = "/reset"


}

const val PUSH_TYPE_PING = 0;
const val PUSH_TYPE_PONG = 1;
const val PUSH_TYPE_REQ = 2;
const val PUSH_TYPE_RES = 3;
const val PUSH_TYPE_NOTIFY = 4;
const val PUSH_TYPE_NOTIFY_ACK = 5;
const val PUSH_TYPE_ACK = 6;


private const val POLL_MSG_TIMEOUT = 8
private const val POLL_NOT_LOGIN = 15

private suspend fun doPoll() {

    while (true) {

        try {
            val msg = poll()

            when (msg.type) {
                PUSH_TYPE_PING -> send(PUSH_TYPE_PONG, msg.seq, EC_OK, byteArrayOf())

                PUSH_TYPE_NOTIFY_ACK -> {
                    send(PUSH_TYPE_ACK, msg.seq, EC_OK, byteArrayOf())
                    scope.launchCatchAny { onNotify(msg) }
                }

                PUSH_TYPE_NOTIFY -> {
                    scope.launchCatchAny { onNotify(msg) }
                }

                PUSH_TYPE_REQ -> {
                    send(PUSH_TYPE_ACK, msg.seq, EC_OK, byteArrayOf())
                    scope.launchCatchAny { onRequest(msg) }
                }
            }

        } catch (e: ServiceException) {
            if (e.info.errCode == POLL_MSG_TIMEOUT) {
                continue
            } else if (e.info.errCode == POLL_NOT_LOGIN) {
                loginKeep()
            }
            e.printStackTrace()
        } catch (e: UnknownHostException) {
            e.printStackTrace()
            wifiRestart()
            delay(5000)
        } catch (e: Exception) {
            e.printStackTrace()
            delay(10 * 1000)
        }

    }
}

private suspend fun loginKeep() {
    while (true) {
        try {
            login()
            return
        } catch (e: UnknownHostException) {
            e.printStackTrace()
            wifiRestart()
            delay(5000)
        } catch (e: IOException) {
            e.printStackTrace()
            log("${stringById(R.string.t1733460848_6)}")
        } catch (e: ServiceException) {
            e.printStackTrace()
            showErr("${stringById(R.string.t1733460848_7)}", e)
            return
        } catch (e: Exception) {
            e.printStackTrace()
        }
        delay(5 * 1000)
    }
}

@OptIn(ExperimentalSerializationApi::class)
private suspend fun onNotify(msg: PushReq) {
    log("notify:$msg")

    when (msg.cmd) {
        ServerCmd.RESET -> {
            withContext(Dispatchers.Main) {
                reset { }
            }
        }

        ServerCmd.APP_UPDATE -> {
            val req = Cbor.decodeFromByteArray<UpdateReq>(msg.body)
            doUpdateApp(req.firmId)
        }


    }
}


@Serializable
private class ChangeReq(
    val value: Int
)

@Serializable
private class UpdateReq(
    val firmId: Long
)

suspend fun doUpdateMcu(id: Long) {
    runProg(stringById(R.string.text_update2), stringById(R.string.text_update_bin)) { dialog ->
        dialog.setMsg(stringById(R.string.text_download_firm))
        val buf = downloadBin(id)
        ota(buf) { dialog.setMsg(it) }
    }
}

suspend fun doUpdateApp(id: Long) {
    runProg(stringById(R.string.text_update2), stringById(R.string.text_update_apk)) { dialog ->
        val path = downloadApk(id, "client.apk") {
            dialog.setMsg("${stringById(R.string.text_down_prog)}:$it%")
        }

        delay(50)
        dialog.setMsg(stringById(R.string.text_install))
        delay(50)
        installApk2(path)
    }
}

@OptIn(ExperimentalSerializationApi::class)
private suspend fun onRequest(msg: PushReq) {
    log("request:$msg")
    val start = System.currentTimeMillis()
    try {
        val body = when (msg.cmd) {

            ServerCmd.VEND_REFRESH -> {
                postEvent(ResetReplenishEvent())
                byteArrayOf()
            }

            ServerCmd.STATUS_DEVICE -> {
                Cbor.encodeToByteArray(DeviceStatus.get())
            }

            ServerCmd.STATUS_COIN -> {
                Cbor.encodeToByteArray(Coin.queryCoinInfo())
            }

            ServerCmd.STATUS_BILL -> {
                Cbor.encodeToByteArray(Bill.stackerInfo())
            }

            ServerCmd.APP_VERSION -> {
                Cbor.encodeToByteArray(Version)
            }

            ServerCmd.MCU_UPDATE -> {
                val req = Cbor.decodeFromByteArray<UpdateReq>(msg.body)
                doUpdateMcu(req.firmId)
                byteArrayOf()
            }

            ServerCmd.COIN_CHANGE -> {
                val req = Cbor.decodeFromByteArray<ChangeReq>(msg.body)
                Coin.change(req.value)
                byteArrayOf()
            }

            ServerCmd.GET_VEND -> {
                Cbor.encodeToByteArray(Vend2Fragment.vendFlag)
            }

            ServerCmd.UPLOAD_ORDER -> {
                uploadOrder()
                byteArrayOf()
            }

            ServerCmd.SCREEN_SHOT -> {
                withContext(Dispatchers.Main) {
                    BaseActivity.INSTANCE.viewCap2()
                }
            }

            else -> throw IllegalStateException("unknown req${msg.cmd}")
        }
        log("exec finish:${System.currentTimeMillis() - start}")

        send(PUSH_TYPE_RES, msg.seq, EC_OK, body)
    } catch (e: Exception) {
        e.printStackTrace()

        val errInfo = ErrInfo(1, e.message ?: "")

        send(PUSH_TYPE_RES, msg.seq, EC_FAIL, Cbor.encodeToByteArray(errInfo))
    }
    log("req finish: ${System.currentTimeMillis() - start}")
}











































